import asyncio
import json
import logging
from playwright.async_api import async_playwright

logging.basicConfig(level=logging.INFO)


async def extract_tiktok_video(url: str, timeout_seconds=120) -> str:
    video_url = None

    def match_tiktok_video(req_url: str) -> bool:
        """匹配 TikTok 视频资源请求（排除 webapp 占位流）"""
        return (
            ("tiktokcdn.com" in req_url)
            and ("mime_type=video_mp4" in req_url or ".mp4" in req_url)
            and ("-webapp." not in req_url)   # 🚫 过滤掉 webapp 占位流
        )

    def find_mp4_in_json(data):
        """递归搜索 JSON 中的 mp4/mime_type=video_mp4 链接"""
        links = []
        if isinstance(data, dict):
            for k, v in data.items():
                if isinstance(v, str) and (
                    "mp4" in v or "mime_type=video_mp4" in v
                ):
                    if "-webapp." not in v:  # 🚫 过滤 webapp
                        links.append(v)
                else:
                    links.extend(find_mp4_in_json(v))
        elif isinstance(data, list):
            for item in data:
                links.extend(find_mp4_in_json(item))
        return links

    async with async_playwright() as p:
        # 用 iPhone 14 模拟环境
        iphone_14 = p.devices["iPhone 14"]

        browser = await p.chromium.launch(
            headless=True,
            args=["--no-sandbox", "--disable-dev-shm-usage"],
        )
        context = await browser.new_context(**iphone_14)
        page = await context.new_page()

        # 1️⃣ 请求拦截
        async def handle_route(route, request):
            nonlocal video_url
            req_url = request.url
            if match_tiktok_video(req_url):
                logging.info(f"🎯 捕获视频请求: {req_url}")
                video_url = req_url
            await route.continue_()

        await context.route("**/*", handle_route)

        # 2️⃣ 响应监听（抓 API 返回的 JSON）
        async def handle_response(response):
            nonlocal video_url
            try:
                if "item/detail" in response.url and "application/json" in response.headers.get("content-type", ""):
                    body = await response.text()
                    data = json.loads(body)

                    # API 里返回的视频地址
                    video_info = data.get("itemInfo", {}).get("itemStruct", {}).get("video", {})
                    if not video_info:
                        return

                    play_addr = video_info.get("playAddr")
                    download_addr = video_info.get("downloadAddr")

                    # 优先无水印下载地址
                    video_url = download_addr or play_addr
                    logging.info(f"✅ 捕获真实视频地址: {video_url}")
            except Exception as e:
                logging.warning(f"⚠️ 解析响应失败: {e}")

        page.on("response", handle_response)

        # 3️⃣ 访问页面
        logging.info(f"🔍 正在访问: {url}")
        try:
            await page.goto(url, wait_until="domcontentloaded", timeout=30000)
        except Exception as e:
            logging.error(f"❌ 页面加载失败: {e}")

        # 4️⃣ 模拟点击视频触发播放
        try:
            await page.click("video")
            await page.evaluate("document.querySelector('video')?.play()")
            logging.info("▶️ 已触发视频播放")
        except Exception as e:
            logging.warning(f"⚠️ 视频播放触发失败: {e}")

        # 5️⃣ 循环等待视频地址
        logging.info(f"⏳ 正在等待视频地址（最多 {timeout_seconds} 秒）...")
        for _ in range(timeout_seconds):
            if video_url:
                logging.info("✅ 已捕获视频地址，提前结束等待")
                break
            # 兜底检测 DOM 里的 <video> 标签
            try:
                src = await page.eval_on_selector("video", "el => el.src") or None
                if src and "mp4" in src and "-webapp." not in src:
                    logging.info(f"✅ 从 DOM 获取视频地址: {src}")
                    video_url = src
                    break
            except:
                pass
            await asyncio.sleep(1)

        await browser.close()

    if video_url:
        logging.info(f"🎬 最终视频地址: {video_url}")
        return video_url
    else:
        logging.warning("⚠️ 未能获取到视频地址")
        return url


if __name__ == "__main__":
    test_url = "https://www.tiktok.com/@scout2015/video/6718335390845095173"
    result = asyncio.run(extract_tiktok_video(test_url))
    print("最终结果:", result)
